#ifndef __U_LOG_SYSTEM_H__
#define __U_LOG_SYSTEM_H__

/***
  u-log-system.hpp logger function collection
  Copyright (C) 2013  Renweu Gao

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ***/

#include "u-base.hpp"

#include <fstream>
#include <string>
#include <cstdarg>
#include <cmath>

namespace u {

    class log {
        private:
            static unsigned char _flag;
            static char * _filename;
            static std::ofstream _ofs;
            static char * _prompt;

            log() { };
        public:

            static bool open(unsigned char flag, const std::string &filename) {
                bool ret = true;
                if ((flag & D) == D) {
                    _flag = (u::T | u::FLUSH);
                } else {
                    _flag = flag;
                    if ((flag & (F | T)) == (F | T) || (flag & F) == F) {
                        if (filename.empty()) {
                            ret = false;
                        } else {
                            _filename = string_dup(filename);
                            _ofs.open(_filename);
                        }
                    }
                }
                if (ret)
                    _flag |= u::O;
                return ret;
            }

            static void reopen() {
                if (opened())
                    return;
                _flag |= u::O;
            }

            static void close() {
                if (!opened())
                    return;
                _flag &= 0xF7;
            }

            static void term(const char* format, ...) {
                if (format != NULL) {
                    char * msg = NULL;
                    va_list arg_list;
                    va_start(arg_list, format);
                    msg = va_format(format, arg_list);
                    va_end(arg_list);
                    std::cout << msg << std::flush;
                }
            }

            template <size_t num = 10, bool brief = true >
                static void line(size_t ith, size_t total, const char * format, ...) {
                    assert(num != 0 && total != 0);
                    char * msg = NULL;
                    va_list arg_list;
                    va_start(arg_list, format);
                    msg = va_format(format, arg_list);
                    va_end(arg_list);

                    ++ ith;

                    double size = total / static_cast<double> (num);
                    size_t div = static_cast<size_t> (std::ceil(ith / size) - 1);
                    size_t step = std::ceil(num / static_cast<double> (total)) - 1;
                    if (step == 0)
                        step = 1;
                    static size_t len = 0;

                    if (_prompt != NULL && len != num) {
                        string_free(& _prompt);
                    }
                    if (_prompt == NULL) {
                        _prompt = new char[num + 1];
                        len = num;
                        ::std::fill_n(_prompt, len, '+');
                        _prompt[len] = '\0';
                    }

                    if (div >= len) {
                        div = len - 1;
                    }
                    if (div > 0)
                        std::fill_n(_prompt, div, '+');
                    if (_prompt[div] == 'x' || ith == total) {
                        _prompt[div] = '+';
                    } else {
                        _prompt[div] = 'x';
                    }
                    _prompt[div + 1] = '\0';
                    char *message = NULL;
                    char *_format = NULL;
                    if (brief) {
                        _format = u::format("%%s [%%-%us, %%u\%]\r", num);
                        message = u::format(_format, msg, _prompt, (ith * 100 / total));
                    } else {
                        _format = u::format("%%s [%%-%us, %%u / %%u, %%u\%]\r", num);
                        message = u::format(_format, msg, _prompt, ith, total, (ith * 100 / total));
                    }

                    u::log::term("%s", message);
                    u::string_free(&_format);
                    u::string_free(&message);
                }

            ~log() {
                string_free(& _filename);
                string_free(& _prompt);
                if (_ofs.is_open())
                    _ofs.close();
            }

            template <typename T = const std::string &>
                static void print(T msg, unsigned char flag = u::D) {
                    if (!opened())
                        return;
                    if ((flag & D) == D) {
                        if ((_flag & (u::F | u::T)) == (u::F | u::T)) {
                            _ofs << msg;
                            std::cout << msg;
                            if ((flag & u::FLUSH) == u::FLUSH)
                                std::cout << std::flush;
                        } else if ((_flag & u::F) == u::F && _ofs.is_open()) {
                            _ofs << msg;
                        } else if ((_flag & u::T) == u::T) {
                            std::cout << msg;
                            if ((flag & u::FLUSH) == u::FLUSH)
                                std::cout << std::flush;
                        }
                    } else if ((flag & (u::F | u::T)) == (u::T | u::F)) {
                        if (_ofs.is_open())
                            _ofs << msg;
                        std::cout << msg;
                        if ((flag & u::FLUSH) == u::FLUSH)
                            std::cout << std::flush;
                    } else if ((flag & u::F) == u::F && _ofs.is_open()) {
                        _ofs << msg;
                    } else if ((flag & u::T) == u::T) {
                        std::cout << msg;
                        if ((flag & u::FLUSH) == u::FLUSH)
                            std::cout << std::flush;
                    }
                }

            template <unsigned char flag = D>
                static void echo(const char *format, ...) {
                    if (opened() && format !=NULL) {
                        char * msg = NULL;
                        va_list arg_list;
                        va_start(arg_list, format);
                        msg = u::va_format(format, arg_list);
                        va_end(arg_list);
                        print(msg, flag);
                    }
                }

            static bool opened() {
                return ((_flag & O) == O);
            }

            template <unsigned char flag = D>
                static void message(const char* format, ...) {
                    if (opened() && format!=NULL) {
                        std::string msg("...... message ......\n");
                        va_list arg;
                        va_start(arg, format);
                        msg.append(va_format(format, arg));
                        va_end(arg);
                        msg.append("\n...... **end** ......\n");
                        print(msg, flag);
                    }
                }

            template <unsigned char flag = D>
                static void warning(const char* format, ...) {
                    if (opened() && format!=NULL) {
                        std::string msg("...... warning ......\n");
                        va_list arg;
                        va_start(arg, format);
                        msg.append(va_format(format, arg));
                        va_end(arg);
                        msg.append("\n...... **end** ......\n");
                        print(msg, flag);
                    }
                }

            template <unsigned char flag = D>
                static void error(const char* format, ...) {
                    if (opened() && format!=NULL) {
                        std::string msg("****** error ******\n");
                        va_list arg;
                        va_start(arg, format);
                        msg.append(va_format(format, arg));
                        va_end(arg);
                        msg.append("\n****** **end** ******\n");
                        print(msg, flag);
                    }
                }

            template <unsigned char flag = D>
                static void fatal(const char*format, ...) {
                    if (opened() && format!=NULL) {
                        std::string msg("###### fatal ######\n");
                        va_list arg;
                        va_start(arg, format);
                        msg.append(va_format(format, arg));
                        va_end(arg);
                        msg.append("\n###### **end** ######\n");
                        print(msg, flag);
                    }
                }
    };

    unsigned char log::_flag = 0x00;
    char * log::_filename = NULL;
    std::ofstream log::_ofs;
    char *log::_prompt = NULL;

}

#endif
